Java在运行时识别对象和类的信息的行为的有两种方式
- RTTI(Run-Time Type Identification) 他假定我们在编译时已经知道了所有的类型
- 反射 它允许我们在运行时发现和使用类的信息
Class对象
Class对象包含了与类有关的信息,就是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。
类是程序的一部分,每个类都有一个class对象,每当编写或者编译了一个新类,就会产生一个class对象(被保存在一个同名的.class文件中),为了生成这个类的对象,运行这个程序的java虚拟机(jvm)将使用被称为“类加载器”的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是jvm实现的一部分。原生类加载器加载的是所谓的可信类,包括java api类,它们通常是从本地盘加载的,这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(特殊方式加载类,或者从网络中下载类),那么你有一种方式可以挂接额外的类加载器。
所有的类都是在对其第一次使用使,动态的加载到jvm中去的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明了构造器也是类的静态方法。
因此,java程序在它开始运行前并非完全加载,其各个部分是在必需时才加载的(动态加载)。
类加载器先回检查这个类的class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加的类加载器可能会在数据库中查找字节码)。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良的java代码(这是java中用于安全防范目的的措施之一)。
一旦某个类的class对象被载入内存,它就被用来创建这个类的所有对象
Class.forName(String)
这个方法是取得Class
对象的引用的一种方法,它是一个包含目标类的文本名(大小写敏感)的String
作为参数,返回一个Class
对象的引用,在类尚未加载之前调用这个方法,会先去加载这个类,Class
包含很多有用的方法
- getName() 返回全限定的类名
- getSimpleName() 不包含包名的类名
- getCanonicalName() 全限定的类名
- newInstance() 是实现虚拟构造器的一种途径,虚拟构造器允许你声明:”我不知道你的确切类型,但是无论如何你要正确的创建你自己”,另外使用这个方法来创建的类,必须带有默认的构造器。
- getSuperclass()查询其直接基类
类字面常量
使用className.class
使用这种方式创建class
对象的引用时,不会自动初始化该class
对象(不会加载类),为了使用类而做的准备工作实际包含三个步骤:
- 加载 由类家灾区执行的,该步骤将查找字节码(通常在
classpath
所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个class
对象
- 链接 在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用
- 初始化 如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块
初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行。
初始化有效的实现了尽可能的”惰性”
关于static final
关键字
如果一个static final
值是”编译期常量”,这个值不需要对类的初始化就可以被读取。
如果一个static
域不是final
的,那么对他访问前,要先进行链接(为这个域分配存储空间)和初始化(初始化存储空间)
泛化的Class引用
Class
引用总是指向某个Class
对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还可以包含该类的静态成员,因此,Class
引用表示的就是它所指向的对象的确切类型,而该对象便是Class
类的一个对象。
public class GenericClassReferences { public static void main(String[] args) { Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; intClass = double.class; } }
|
普通的类引用不会产生警告信息,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class
对象,通过泛型语法,可以让编译器强制执行额外的类型检查。
为了在泛化的Class
引用时放松限制,可以使用通配符 “?”,它是java泛型的一部分,表示“任何事物”
public class WildcardClassReferences { public static void main(String[] args) { Class<?> intClass = int.class; intClass = double.class; } }
|
Class<?>优于平凡的Class,即便他们是等价的,而且平凡的Class如你所见,不会产生编译器警告信息,Class<?>的好处是它表示你并非是碰巧或者疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本
为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。
public class BoundedClassReferences { public static void main(String[] args) { Class<? extends Number> bounded = int.class; bounded = double.class; bounded = Number.class; } }
|
向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果你操作有误,稍后立即就会发现它。
新的转型语法
cast()
public class ClassCasts { public static void main(String[] args) { Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); h = (House)b; } }
|
转型前先做类型检查
传统的类型转换,如果执行了一个错误的类型转换,就会抛出一个ClassCastException
异常。
java要执行类型检查(类型安全的向下转型),之所以叫向下转型,是因为类层次结构图从来就是这么排序的,如果将Circle
类型转换为Shape
类型,就称为向上转型,反之为向下。因为知道Circle
就是一个Shape
,所以类型编译器允许你自由的做向上转型的赋值操作而不需要任何显示的转型操作。
但是在编译期,编译器不知道这个Shape
的具体类型,他可以是一个Shape
,也可能是他的一个子类型,因此如果不使用显示的类型转换,编译器就不允许你执行向下转型赋值,以告知编译期你有额外的信息,这些信息使你知道该类型是某种特定类型(编译器将检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类的子类的类型上)
因此可以使用instanceof
关键字,以提问的方式先判断这个类的类型
if(x instanceof Dog){ ((Dog)x).bark(); }
|
动态的instanceof isInstance()
public class PetCount3 { static class PetCounter extends LinkedHashMap<Class<? extends Pet>,Integer> { public PetCounter() { super(MapData.map(LiteralPetCreator.allTypes, 0)); } public void count(Pet pet) { for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()) if(pair.getKey().isInstance(pet)) put(pair.getKey(), pair.getValue() + 1); } public String toString() { StringBuilder result = new StringBuilder("{"); for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()) { result.append(pair.getKey().getSimpleName()); result.append("="); result.append(pair.getValue()); result.append(", "); } result.delete(result.length()-2, result.length()); result.append("}"); return result.toString(); } } public static void main(String[] args) { PetCounter petCount = new PetCounter(); for(Pet pet : Pets.createArray(20)) { printnb(pet.getClass().getSimpleName() + " "); petCount.count(pet); } print(); print(petCount); } }
|
PetCounter3
预加载了LiteralPetCreator.allTypes
,这个是一个类的数组中的类型,可以看到使用isInstance()
方法可以使我们不在需要instance表达式,只要改变LiteralPetCreator.allTypes
数组中的内容就行了。
递归计数 Class.isAssignableFrom()
在上面的PetCounter3
的map
中预加载了所有不同的的Pet
类,与预加载映射表不同的是,我们可以使用Class.isAssignableFrom()
,并创建一个不局限于对Pet
的通用计数工具。
public class TypeCounter extends HashMap<Class<?>,Integer>{ private Class<?> baseType; public TypeCounter(Class<?> baseType) { this.baseType = baseType; } public void count(Object obj) { Class<?> type = obj.getClass(); if(!baseType.isAssignableFrom(type)) throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + baseType); countClass(type); } private void countClass(Class<?> type) { Integer quantity = get(type); put(type, quantity == null ? 1 : quantity + 1); Class<?> superClass = type.getSuperclass(); if(superClass != null && baseType.isAssignableFrom(superClass)) countClass(superClass); } }
|
Class.isAssignableFrom()
如果调用这个方法的class或接口 与 参数cls表示的类或接口相同,或者是参数cls表示的类或接口的父类,则返回true。例:
System.out.println(ArrayList.class.isAssignableFrom(Object.class)); System.out.println(Object.class.isAssignableFrom(ArrayList.class));
|
count()
方法获取其参数的Class,然后使用isAssignableFrom()
来执行运行时的检查,以校验你传递的对象确实是我们感兴趣的继承结构。countClass()
首先对该类的确切类型计数,然后,如果其超累可以赋值给baseType
,countClass
将其超类上递归计数。
注册工厂
//其实下面两段没太看懂
生成Pet
继承结构中的对象存在着一个问题,即每次向该继承结构添加新的Pet
类型时,必须将其添加为LiteralPetCreator
中的项。如果在系统中已经存在了继承结构的常规的基础,然后在其上要添加更多的类,那么就有可能会出现问题。
你可能会考虑在每个子类中添加静态初始化器,以使得该初始化器可以将它的类添加到某个List
中,遗憾的是,静态初始化器只有在类首先被加载的情况下才能被调用,因此你就碰上了是”先有鸡还是先有蛋”的问题:生成器在其列表中不包含这个类,因此它永远不能创建这个类的对象,而这个类也就不能被加载并置于这个列表中。
在这里引入工厂方法模式,将对象的创建交给类自己去实现,工厂方法可以被多态的调用,从而为你创建恰当的对象。
public interface Factory<T> { T create(); }
|
泛型参数T
使得create()
可以在每种Factory
实现中返回不同的类型。
在下面的示例中,基类Part
包含一个工厂对象的列表。对于这个由createRandom()
方法产生的类型,它的工厂都被添加到了partFactories
中去,从而被注册到了基类中。
class Part { public String toString() { return getClass().getSimpleName(); } static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>(); static { partFactories.add(new FuelFilter.Factory()); partFactories.add(new AirFilter.Factory()); partFactories.add(new CabinAirFilter.Factory()); partFactories.add(new OilFilter.Factory()); partFactories.add(new FanBelt.Factory()); partFactories.add(new PowerSteeringBelt.Factory()); partFactories.add(new GeneratorBelt.Factory()); } private static Random rand = new Random(47); public static Part createRandom() { int n = rand.nextInt(partFactories.size()); return partFactories.get(n).create(); } } class Filter extends Part {} class FuelFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<FuelFilter> { public FuelFilter create() { return new FuelFilter(); } } } class AirFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<AirFilter> { public AirFilter create() { return new AirFilter(); } } } class CabinAirFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<CabinAirFilter> { public CabinAirFilter create() { return new CabinAirFilter(); } } } class OilFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<OilFilter> { public OilFilter create() { return new OilFilter(); } } } class Belt extends Part {} class FanBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<FanBelt> { public FanBelt create() { return new FanBelt(); } } } class GeneratorBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<GeneratorBelt> { public GeneratorBelt create() { return new GeneratorBelt(); } } } class PowerSteeringBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<PowerSteeringBelt> { public PowerSteeringBelt create() { return new PowerSteeringBelt(); } } } public class RegisteredFactories { public static void main(String[] args) { for(int i = 0; i < 10; i++) System.out.println(Part.createRandom()); } }
|
instanceof与Class的等价性
在查询类型信息的时候,以instanceof
的形式和直接比较Class
对象有一个很重要的差别。
class Base {} class Derived extends Base {} public class FamilyVsExactType { static void test(Object x) { print("Testing x of type " + x.getClass()); print("x instanceof Base " + (x instanceof Base)); print("x instanceof Derived "+ (x instanceof Derived)); print("Base.isInstance(x) "+ Base.class.isInstance(x)); print("Derived.isInstance(x) " + Derived.class.isInstance(x)); print("x.getClass() == Base.class " + (x.getClass() == Base.class)); print("x.getClass() == Derived.class " + (x.getClass() == Derived.class)); print("x.getClass().equals(Base.class)) "+ (x.getClass().equals(Base.class))); print("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); } } Testing x of type class typeinfo.Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class typeinfo.Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true */
|
在上述代码中,instanceof
和isInstance()
结果一样,equals
和==
结果一样,instanceof
保持了类型的概念,而用==
就没有考虑继承。
反射:运行时的类信息
如果不知道某个对象的确切信息,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI去识别它,并利用这些信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。
初看起来这似乎不是个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对象的引用,事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假如你从磁盘文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表了一个类。既然这个类在编译器为你的程序生成代码之后很久才会出现,那么怎样才能使用这样的类呢?
此外人们还希望提供在跨网络的远程平台上创建和运行对象的能力,这也是反射出现的动机之一。
Class
类与java.lang.reflect
类库一起对反射的概念进行了支持,该类库包括了Field
、Method
、以及Constructor
类(都实现了Member接口)。这些类型的对象是由jvm在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor
创建新的对象,用get()
和set()
方法读取和修改与Field
对象关联的字段,用invoke()
方法调用与Method
对象关联的方法。另外,还可以调用getField()
、getMethod()
、getConstructor()
等很便利的方法以返回表示字段、方法、以及构造器的对象的数组,这样匿名对象的类信息就能在运行时被完全确定下来,而编译时不需要知道任何事情。
当通过反射与一个未知类型的对象打交道时,jvm
只是简单的检查这个对象,看它属于哪个特定的类(就想RTTI那样)。在用它做其他事情之前必须先加载那个类的Class
对象,因此那个类的.class文件对于JVM来说是必须可获取的,要么本地要么网络。所以反射和RTTI真正的区别在于,对RTTI来说,编译器在编译时打开和检查.class文件。(换句话说,我们可以用”普通”方式调用对象的所有方法)而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
类方法提取器
通常你不需要直接使用反射工具,但是它们在你需要创建更加动态的代码时会很有用,反射在Java
中是用来支持其他特性的,例如对象序列化和JavaBean
。但是如果能动态的提取某个类的信息有的时候还是很有用的。请考虑方法提取器。
Class的getMethod()
和getConstructor()
方法分别返回Method
对象的数组和Constructor
对象的数组。这两个类都提供了深层方法,用以解析其对象所代表的方法,并获取其名字。输入参数以及返回值。
public class ShowMethods { private static String usage = "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method method : methods) print( p.matcher(method.toString()).replaceAll("")); for(Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); lines = methods.length + ctors.length; } else { for(Method method : methods) if(method.toString().indexOf(args[1]) != -1) { print( p.matcher(method.toString()).replaceAll("")); lines++; } for(Constructor ctor : ctors) if(ctor.toString().indexOf(args[1]) != -1) { print(p.matcher( ctor.toString()).replaceAll("")); lines++; } } } catch(ClassNotFoundException e) { print("No such class: " + e); } } } public static void main(String[]) public native int hashCode() public final native Class getClass() public final void wait(long,int) throws InterruptedException public final void wait() throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public final native void notify() public final native void notifyAll() public ShowMethods() */
|
动态代理
代理是基本的设计模式之一,它是你为了提供额外的的或不同的操作而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色,下面一个是用来展示代理结构的简单示例:
interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject implements Interface { public void doSomething() { print("doSomething"); } public void somethingElse(String arg) { print("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied) { this.proxied = proxied; } public void doSomething() { print("SimpleProxy doSomething"); proxied.doSomething(); } public void somethingElse(String arg) { print("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } } class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } } doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo */
|
consumer()
方法接受Interface
,所以它无法知道正在获得的到底是RealObject
还是SimpleProxy
,因为二者都实现了Interface
。但是SimpleProxy
已经被插入到了客户端和RealObject
之间,因此它会执行操作,然后调用RealObject
上相同的方法。
在任何时刻,只要你想将额外的操作从”实际”的对象中分离到不同的地方,特别当你希望能够很容易的做出修改,从没有使用额外操作转为使用这些操作,或者反过来,代理就显得很有用(设计模式的关键就是封装修改-因此你需要修改事务以证明这种模式的正确性)。例如,如果你希望跟踪对RealObject
中方法的调用,或者希望度量这些调用的开销,那么你应该怎样做呢?这些代码肯定是你不希望将其合并到应用中的代码,因此代理使得你可以很容易地添加和删除它们。
动态代理
Java
的动态代理比代理的思想更向前迈进了一步,因为他可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); if(args != null) for(Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{ Interface.class }, new DynamicProxyHandler(real)); consumer(proxy); } }
|
通过调用静态方法Proxy.newProxyInstance()
可以创建动态代理,这个方法需要得到一个类加载器(通常可以从已被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler
接口的一个实现,动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个”实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将其请求转发。